package com.jason.system.common.filter;

import com.auth0.jwt.exceptions.TokenExpiredException;
import com.jason.system.common.jwt.JwtToken;
import com.jason.system.common.util.JwtUtil;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import sun.security.util.SecurityConstants;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

/**
 * @author jie
 * @version 1.0
 * success come from self-discipline
 * @date 2021/3/22 22:50
 * 描述: JWT核心过滤器配置
 * 所有的请求都会先经过Filter，继承官方的BasicHttpAuthenticationFilter，并且重写鉴权的方法
 * 该过滤器主要有三步：
 * 1.检验请求头是否带有 Token: ((HttpServletRequest) request).getHeader(“Token”)
 * 2.如果带有 Token ，则执行 Shiro 中的 login() 方法，该方法将导致：
 * 将 Token 提交到 Realm 中进行验证（执行自定义的Realm中的方法）；
 * 如果没有 Token，则说明当前状态为游客状态或者其他一些不需要进行认证的接口
 * 3.如果在 Token 校验的过程中出现错误，如：Token 校验失败，
 * 那么我会将该请求视为认证不通过，则重定向到 /unauthorized/**
 */
@Component
public class JwtFilter extends BasicHttpAuthenticationFilter {

  private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);

  /**
   * 跨域支持
   * @param request 请求
   * @param response 相应
   * @return bool
   * @throws Exception 异常
   */
  @Override
  protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
    httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
    httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
    // 跨域时会首先发送一个option请求，这里我们给option请求直接返回正常状态
    if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
      httpServletResponse.setStatus(HttpStatus.OK.value());
      return false;
    }
    return super.preHandle(request, response);
  }

  /**
   * 检测header中是否含有authorization字段
   * 是否含有token,为true则不是正在请求登录验证
   */
  @Override
  protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
    String auth = getAuthzHeader(request);
    String name = JwtUtil.getUsernameFromToken(auth);
    try {
      JwtUtil.verify(auth,name);
    }catch (Exception e){
      logger.info("--->>>JwtFilter::isLoginAttempt拦截到头部authorization信息,token过期，请重新登录");
      responseError(response);
    }
    logger.info("--->>>JwtFilter::isLoginAttempt拦截到头部authorization信息:[{}]",auth);
    return auth != null && !"".equals(auth);
  }

  /**
   * 是否允许访问
   * 如果请求头带有token，则对token进行检查，否则直接放行
   */
  @Override
  protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    //判断请求头是否带有token，有token且验证token合法性，后执行executeLogin
    logger.info("--->>>JwtFilter::isAccessAllowed方法请求是否允许访问该接口...");
    if(isLoginAttempt(request,response)){
      // 执行executeLogin执行登录逻辑，并检测token的正确性
      try {
        executeLogin(request,response);
      }catch (Exception e){
        logger.error("Error! {}", e.getMessage());
      }
    }
    // 如果不存在 token ，则可能是执行登录操作/游客访问状态，所以直接放行
    return true;
  }

  /**
   * 执行验证操作,验证token合法性
   */
  @Override
  protected boolean executeLogin(ServletRequest request, ServletResponse response)  {
    //或取请求头部authorization字段中的值，默认获取token信息(Bearer ....)
    String token = getAuthzHeader(request);
    logger.info("--->>>JwtFilter::isAccessAllowed拦截到认证token信息:[{}]",token);
    //自定义jwtToken实现AuthenticationToken接口
    JwtToken jwtToken = new JwtToken(token);
    // 提交给 自定义的shirorealm 进行登录验证,如果错误,会抛出异常
    getSubject(request,response).login(jwtToken);
    // 如果没有抛出异常,则代表登录成功,返回true,都可以进入到controller中执行方法
    return true;
  }

  /**
   * 非法请求将跳转到 "/unauthorized/**"
   */
  private void responseError(ServletResponse response) {
    try {
      HttpServletResponse resp = (HttpServletResponse) response;
      resp.setStatus(500);
      resp.sendRedirect("/api/unauthorized");
    } catch (UnsupportedEncodingException e) {
      logger.error("Error! {}", e.getMessage());
    } catch (IOException e) {
      logger.error("Error! {}", e.getMessage());
    }
  }
}
